const LLM_LOCAL_KEY = 'd5_llm_localdisk';

class SettingUI extends d5.D5UI
{
    setting_btn = null;
    setting_ui = null;
    constructor(){
        super('resource/ui/setting.json')
        
        Object.defineProperty(LLMInput.prototype, "height", {
            get: function () {
                return 64;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(LLMInput.prototype, "width", {
            get: function () {
                return 64;
            },
            enumerable: true,
            configurable: true
        });
    }

    onLoaded(){
        super.onLoaded();

        this.setting_btn.touchEnable = true;

        this.setting_btn.addEventListener(d5.TouchEvent.TOUCH_TAP,(e)=>{
            if(this.setting_ui==null)
            {
                this.setting_ui = new LLMSetting();
            }else{
                this.setting_ui.dispose();
                this.setting_ui = new LLMSetting();
            }
            D5Power.addChild(this.setting_ui)
        })
    }
}


class LLMInput extends d5.DisplayContainer {
    constructor() {
        super()
        Object.defineProperty(LLMInput.prototype, "height", {
            get: function () {
                return 48;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(LLMInput.prototype, "width", {
            get: function () {
                return 665;
            },
            enumerable: true,
            configurable: true
        });
    }
}

class LLMBase extends d5.D5UI {

    shp_bg = null;
    shp_label_bg = null;
    txt_label = null;
    txt_input = null;
    route = "";
    _field_map = {}

    constructor() {
        super('resource/ui/llm_base.json');
    }

    onLoaded() {
        super.onLoaded();
        this._box = new d5.D5VBox();
        this._box.padding = 10;
        this._box.setPosition(this.shp_bg.x, this.shp_bg.y);
        this.addChild(this._box);
        this._template = new d5.D5Template();
        this._template.fromComponent(this.shp_bg, this.shp_label_bg, this.txt_label, this.txt_input);
        this.addField("url", "API地址");
        this.addField("model", "模型");
        this.addField("key", "密钥");
    };

    addField(key, label) {
        var input = this._template.getInstanceBind(LLMInput);
        var that = this;
        var onEdit = function () {
            var layer = layui.layer;
            layer.prompt({ title: label, value: input.txt_input.text, formType: 2 }, function (text, index) {
                layer.close(index);
                if (text.indexOf("\n") != -1) {
                    layer.alert("参数请勿换行");
                    return;
                }
                input.txt_input.text = text;
                layui.data(LLM_LOCAL_KEY, { key: that.route + "_" + key, value: text });
            });
        };
        input.txt_label.text = label;
        input.txt_input.text = '';
        input.touchEnable = true;
        input.addEventListener(d5.TouchEvent.TOUCH_TAP, onEdit, 'pointer');
        this._box.addChild(input);
        this._field_map[key] = input;
    };

    getField(key) {
        return this._field_map[key] ? this._field_map[key].txt_input.text : "";
    };

    setField(key, value) {
        if (this._field_map[key]) {
            this._field_map[key].txt_input.text = value;
        }
    };
};

class LLMTool{
    constructor(name, description, type) {
        if (description === void 0) { description = ""; }
        if (type === void 0) { type = "function"; }
        this.need_confirm = false;
        this.type = type;
        this.function = { name: name, description: description, parameters: { type: 'object', required: [], properties: {} } };
        this.application = null;
    }

    addParameters(name, type, description, isRequire) {
        if (isRequire === void 0) { isRequire = false; }
        this.function.parameters.properties[name] = { type: type, description: description };
        if (isRequire) {
            this.function.parameters.required.push(name);
        }
    };
};

class LLMDriver {
    static _instance = null;
    
    constructor(route, spec_model = "") {
        this._route = route;
        this._model = spec_model;
        this.API_URL = "";
        this.API_KEY = "";
        this.API_MODEL = "";
        // LLMSetting will be defined below, so we'll call this after the class is defined
        // We'll initialize this after both classes are defined
        LLMSetting.initDriver(this);
    }

    /**
     * 获取大模型驱动
     * @param {string} route 大模型类型，如果留空则直接获取当前生效的大模型
     * @returns {LLMDriver}
     */
    static getInstance(route = null) {
        if (route == null) {
            return this._instance;
        }
        if (this._instance && this._instance._route == route) {
            return this._instance;
        } else {
            this._instance = new LLMDriver(route);
            return this._instance;
        }
    }

    get info() {
        return "LLM:" + this.route + "\n Model:" + this.API_MODEL;
    }

    get route() {
        return this._route;
    }

    /**
     *
     * @param {string} input 输入内容
     * @param {string} character 角色设定
     * @param {Array} history 历史对话，一个数组，结构为[{role:'角色',content:'内容'}]
     * @param {boolean} useJson 是否使用JSON格式
     * @param {Array} tools 工具列表
     * @returns {Promise}
     */
    chat(input, character = null, history = null, useJson = false, tools = null) {
        if (!this.API_URL || !this.API_KEY || !this.API_MODEL) {
            console.log("============Need setting========" + this.API_KEY);
            // Return a rejected jQuery Deferred object
            let $ = layui.$;
            let deferred = $.Deferred();
            deferred.reject(false);
            return deferred.promise();
        }
        
        let tool_map = {};
        if (tools) {
            tools.forEach(tool => {
                if (!tool.application) {
                    alert("your tool (" + tool.function.name + ") is not complete set.you need set tool.application,please check it");
                }
                tool_map[tool.function.name] = tool;
            });
        }
        
        let that = this;
        let messages = [];
        let $ = layui.$;
        
        if (character != null) {
            messages.push({ role: "system", content: character });
        }

        if (history != null) {
            for (let i = 0, j = history.length; i < j; i++) {
                messages.push(history[i]);
            }
        }

        messages.push({ role: 'user', content: input });

        const options = {
            url: that.API_URL,
            type: 'POST',
            contentType: 'application/json',
            dataType: 'json',
            headers: {
                "Authorization": `Bearer ${that.API_KEY}`,
                "Content-Type": "application/json"
            }
        };

        let requeryData;
        
        if (useJson) {
            requeryData = {
                model: that._model == "" ? that.API_MODEL : that._model,
                messages: messages,
                temperature: 0.3,
                tools: tools,
                response_format: {
                    'type': 'json_object'
                }
            };
        } else {
            requeryData = {
                model: that.API_MODEL,
                messages: messages,
                tools: tools,
                temperature: 0.3
            };
        }
        
        let onResult = function(res) {
            if (res.error) {
                this.reject(res.error.message);
                return;
            }
            if (res && res.choices && res.choices.length > 0) {
                let obj = res.choices[0];
                if (obj.finish_reason == "tool_calls") {
                    messages.push(obj.message);
                    // 工具调用
                    let want_tools = obj.message.tool_calls;
                    let need_confirm = false;
                    for (let i = 0, j = want_tools.length; i < j; i++) {
                        // 服务器需要请求的工具
                        let toolCall = want_tools[i];
                        // 本地的工具映射存储的工具
                        let tool = tool_map[toolCall.function.name];

                        let param = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : null;
                        need_confirm = need_confirm || tool.need_confirm;
                        //console.log(toolCall.function.name+":"+need_confirm+"/"+toolCall.need_confirm);
                        messages.push({
                            role: "tool",
                            tool_call_id: toolCall.id,
                            name: toolCall.function.name,
                            content: param ? tool.application.apply(null, [param]) : tool.application.apply(null)
                        });
                    }
                    if (need_confirm) {
                        layui.layer.confirm("是否继续请求？", (confirm_index) => {
                            layui.layer.close(confirm_index);
                            $.ajax({
                                ...options,
                                data: JSON.stringify(requeryData),
                                success: onResult,
                                error: function(res = null) {
                                    deferred.reject("");
                                }
                            });
                        });
                    } else {
                        $.ajax({
                            ...options,
                            data: JSON.stringify(requeryData),
                            success: onResult,
                            error: function(res = null) {
                                deferred.reject("");
                            }
                        });
                    }
                } else {
                    deferred.resolve(obj.message.content);
                }
            } else {
                deferred.reject("");
            }
        };
        
        // Use jQuery Deferred for better compatibility
        let deferred = $.Deferred();
        
        // Bind the context for onResult to access the deferred object
        let boundOnResult = function(res) {
            if (res.error) {
                deferred.reject(res.error.message);
                return;
            }
            if (res && res.choices && res.choices.length > 0) {
                let obj = res.choices[0];
                if (obj.finish_reason == "tool_calls") {
                    messages.push(obj.message);
                    // 工具调用
                    let want_tools = obj.message.tool_calls;
                    let need_confirm = false;
                    for (let i = 0, j = want_tools.length; i < j; i++) {
                        // 服务器需要请求的工具
                        let toolCall = want_tools[i];
                        // 本地的工具映射存储的工具
                        let tool = tool_map[toolCall.function.name];

                        let param = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : null;
                        need_confirm = need_confirm || tool.need_confirm;
                        //console.log(toolCall.function.name+":"+need_confirm+"/"+toolCall.need_confirm);
                        messages.push({
                            role: "tool",
                            tool_call_id: toolCall.id,
                            name: toolCall.function.name,
                            content: param ? tool.application.apply(null, [param]) : tool.application.apply(null)
                        });
                    }
                    if (need_confirm) {
                        layui.layer.confirm("是否继续请求？", (confirm_index) => {
                            layui.layer.close(confirm_index);
                            $.ajax({
                                ...options,
                                data: JSON.stringify(requeryData),
                                success: boundOnResult,
                                error: function(res = null) {
                                    deferred.reject("");
                                }
                            });
                        });
                    } else {
                        $.ajax({
                            ...options,
                            data: JSON.stringify(requeryData),
                            success: boundOnResult,
                            error: function(res = null) {
                                deferred.reject("");
                            }
                        });
                    }
                } else {
                    deferred.resolve(obj.message.content);
                }
            } else {
                deferred.reject("");
            }
        };
        
        $.ajax({
            ...options,
            data: JSON.stringify(requeryData),
            success: boundOnResult,
            error: function(res = null) {
                deferred.reject("");
            }
        });
        
        return deferred.promise();
    }
}

class LLMSetting extends d5.D5UI {
    constructor() {
        super('resource/ui/llm_setting.json');
        this._db = layui.data(LLM_LOCAL_KEY);
        
        // Initialize UI component properties
        this.btn_model = null;
        this.btn_deepseek = null;
        this.btn_doubao = null;
        this.btn_qwen = null;
        this.btn_close = null;
        this.btn_kimi = null;
        this.btn_glm = null;
        this.btn_get_apikey = null;
        this.rdo_default = null;
        this.extPanel = null;
        this._now_btn = null;
        this._llm_base = null;

        
    }

    onLoaded() {
        super.onLoaded();
        let that = this;

        that.initListener();

        this._llm_base = new LLMBase();
        this._llm_base.setPosition(this.extPanel.x, this.extPanel.y);
        this.removeChild(this.extPanel);

        setTimeout(() => {
            that.showExtPanel();
        }, 200);


        let updateDefaultData = function(api_name) {
            let db = layui.data(LLM_LOCAL_KEY);
            let url = db[that._llm_base.route + "_url"];
            let key = db[that._llm_base.route + "_key"];
            let model = db[that._llm_base.route + "_model"];

            if (!url) {
                url = LLMSetting.getDefault(that._llm_base.route, 'url');
            }

            if (!model) {
                model = LLMSetting.getDefault(that._llm_base.route, 'model');
            }

            if (!url || !key || !model) {
                layui.layer.alert("请先设置模型的调用参数（地址/密钥/模型）", (index) => {
                    layui.layer.close(index);
                    that.rdo_default.selected = false;
                });
                
                return false;
            }
            
            layui.data(LLM_LOCAL_KEY, { key: api_name, value: that._llm_base.route });
            return true;
        }

        
        that.rdo_default.addEventListener(d5.Event.CHANGE, function(e) {
            if (e.data) {
                updateDefaultData('default_llm') && LLMSetting.initDefault();
            }
        });
    }

    static initDefault() {
        let db = layui.data(LLM_LOCAL_KEY);
        let driver;
        let default_llm = db.default_llm;
        if (default_llm) {
            driver = LLMDriver.getInstance(default_llm);
            //console.log("=====setup default llm "+default_llm+"===========")
        }

        return driver;
    }

    static initDriver(driver) {
        let db = this.getConfig(driver.route);
        driver.API_URL = db.API_URL;
        driver.API_MODEL = db.API_MODEL;
        driver.API_KEY = db.API_KEY;
        //trace(`=====initDrvier======${driver.API_URL},${driver.API_KEY},${driver.API_MODEL}=======`);
    }

    static getConfig(route) {
        let obj = { API_URL: '', API_MODEL: '', API_KEY: '' };
        let db = layui.data(LLM_LOCAL_KEY);
        obj.API_URL = db[route + "_url"] ? db[route + "_url"] : this.getDefault(route, 'url');
        obj.API_MODEL = db[route + "_model"] ? db[route + "_model"] : this.getDefault(route, 'model');
        obj.API_KEY = db[route + "_key"] ? db[route + "_key"] : '';
        return obj;
    }


    static getDefault(route, key) {
        switch (route) {
            case 'deepseek':
                if (key == 'url') {
                    return 'https://api.deepseek.com/chat/completions';
                } else if (key == 'model') {
                    return 'deepseek-chat';
                } else if (key == "apikey") {
                    return 'https://platform.deepseek.com/';
                }
                return '';
            case 'kimi':
                if (key == 'url') {
                    return 'https://api.moonshot.cn/v1/chat/completions';
                } else if (key == 'model') {
                    return 'kimi-k2-turbo-preview';
                } else if (key == "apikey") {
                    return 'https://platform.moonshot.cn/console/';
                }
                return '';
            case 'qwen':
                if (key == 'url') {
                    return 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';
                } else if (key == 'model') {
                    return 'qwen-turbo-latest';
                } else if (key == "apikey") {
                    return 'https://bailian.console.aliyun.com/';
                }
                return '';
            case 'doubao':
                if (key == 'url') {
                    return 'https://ark.cn-beijing.volces.com/api/v3/chat/completions';
                } else if (key == 'model') {
                    return 'doubao-seed-1-6-250615';
                } else if (key == "apikey") {
                    return 'https://console.volcengine.com/ark/overview';
                }
                return '';
            case 'glm':
                if (key == "url") {
                    return 'https://open.bigmodel.cn/api/paas/v4/chat/completions';
                } else if (key == "model") {
                    return 'glm-4.5';
                } else if (key == "apikey") {
                    return 'https://console.volcengine.com/ark/overview';
                }
                return "https://bigmodel.cn/console/";
            default:
                return '';
        }
    }

    showExtPanel(btn = null) {
        if (btn == null) {
            btn = this._now_btn;
        }
        this._db = layui.data(LLM_LOCAL_KEY);

        let url;
        let key;
        let model;

        if (btn.extdata) {
            this._llm_base.route = btn.extdata;
            url = this._db[btn.extdata + "_url"] ? this._db[btn.extdata + "_url"] : LLMSetting.getDefault(btn.extdata, 'url');
            model = this._db[btn.extdata + "_model"] ? this._db[btn.extdata + "_model"] : LLMSetting.getDefault(btn.extdata, 'model');
            key = this._db[btn.extdata + "_key"] ? this._db[btn.extdata + "_key"] : '';
            this._llm_base.setField('url', url);
            this._llm_base.setField('model', model);
            this._llm_base.setField('key', key);
        }

        if (LLMDriver.getInstance()) {
            this.rdo_default.selected = this._llm_base.route == LLMDriver.getInstance().route;
        }


        this.addChild(this._llm_base);
    }

    initListener() {
        const btn_list = ['btn_deepseek', 'btn_kimi', 'btn_glm', 'btn_doubao', 'btn_qwen'];
        const btn_label = ['DS', 'KIMI', '智谱', '豆包', '百炼'];
        const btn_model_map = ['deepseek', 'kimi', 'glm', 'doubao', 'qwen'];
        
        if (this.btn_model.parent) {
            this.removeChild(this.btn_model);
        }
        
        let btn_group = [];
        for (let i = 0, j = btn_list.length; i < j; i++) {
            let btn = this.btn_model.clone();
            btn.label = btn_label[i];
            btn_group.push(btn);
            btn.x = this.btn_model.x + this.btn_model.width * i;
            btn.y = this.btn_model.y;
            btn.extdata = btn_model_map[i];
            console.log(btn.x, btn.y);
            if (i == 0) {
                this._now_btn = btn;
                btn.setStatus(3);
            }
            this.addChild(btn);
            this[btn_list[i]] = btn;
        }
        
        let that = this;
        let onTapBtn = function(e) {
            let btn = e.target.d5component;
            if (btn.status == 3) return;
            for (let i = 0, j = btn_group.length; i < j; i++) {
                if (btn_group[i] == btn) continue;
                btn_group[i].setStatus(0);
            }
            btn.setStatus(3);
            that._now_btn = btn;
            that.showExtPanel(btn);
        };

        for (let i = 0, j = btn_group.length; i < j; i++) {
            let btn = btn_group[i];
            btn.addEventListener(d5.TouchEvent.TOUCH_TAP, onTapBtn);
        }

        this.btn_close.once(d5.TouchEvent.TOUCH_TAP, (e) => {
            for (let i = 0, j = btn_group.length; i < j; i++) {
                let btn = btn_group[i];
                btn.removeEventListener(d5.TouchEvent.TOUCH_TAP, onTapBtn);
            }
            that.dispose();
        });

        this.btn_get_apikey.addEventListener(d5.TouchEvent.TOUCH_TAP, (e) => {
            let url = LLMSetting.getDefault(that._now_btn.extdata, 'apikey');
            if (url) window.open(url);
        });
    }
}

// Initialize any LLMDriver instances that were created before LLMSetting was defined
if (LLMDriver._instance) {
    LLMSetting.initDriver(LLMDriver._instance);
}

// Override LLMDriver constructor to properly initialize with LLMSetting
const originalLLMDriverConstructor = LLMDriver.prototype.constructor;
LLMDriver.prototype.constructor = function(route, spec_model = "") {
    originalLLMDriverConstructor.call(this, route, spec_model);
    if (typeof LLMSetting !== 'undefined' && LLMSetting.initDriver) {
        LLMSetting.initDriver(this);
    }
};

LLMSetting.initDefault();